package com.hb0730.boot.admin.security.filter;

import cn.hutool.core.util.StrUtil;
import com.hb0730.boot.admin.base.util.JwtUtil;
import com.hb0730.boot.admin.security.model.OnlineUser;
import com.hb0730.boot.admin.security.service.UserDetailServiceImpl;
import com.hb0730.boot.admin.security.token.JwtTokenRedisCacheProvider;
import com.hb0730.boot.admin.security.token.UserCacheProvider;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Optional;

/**
 * token认证过滤器
 *
 * @author <a href="mailto:huangbing0730@gmail">hb0730</a>
 * @date 2023/1/30
 */
@Slf4j
@RequiredArgsConstructor
@Component
public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
    private final JwtTokenRedisCacheProvider jwtTokenRedisCacheProvider;
    private final UserDetailServiceImpl userDetailService;
    @Lazy
    @Resource
    private UserCacheProvider userCacheProvider;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String _jwtToken = JwtUtil.getTokenByRequest(request);
        //redis中获取信息
        if (StrUtil.isNotBlank(_jwtToken)) {
            Optional<OnlineUser> online = jwtTokenRedisCacheProvider.getOnlineFromToken(_jwtToken);
            if (online.isPresent()) {
                if (SecurityContextHolder.getContext().getAuthentication() == null) {
                    UserDetails userDetails = userDetailService.loadUserByUsername(JwtUtil.getUsername(_jwtToken));
                    UsernamePasswordAuthenticationToken authenticationToken =
                        new UsernamePasswordAuthenticationToken(
                            userDetails.getUsername(),
                            userDetails,
                            userDetails.getAuthorities()
                        );
                    authenticationToken.setDetails(
                        new WebAuthenticationDetailsSource().buildDetails(request)
                    );
                    //放入SecurityContextHolder
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                }
            } else {
                //如果在 Redis 中找不到用户信息，调用 userCacheProvider.clearUser() 清除用户缓存，并设置 HTTP 响应状态为 401（未授权）
                userCacheProvider.clearUser(JwtUtil.getUsername(_jwtToken));
            }
            //jwt续签JWT 续签通常在以下几种情况下进行：
            // 即将过期：JWT 有一个设定的有效期（通常通过 exp 声明）。如果接收到的 Token 快要过期，系统会选择续签，以确保用户继续保持登录状态。
            // 用户活动：在用户进行操作（如访问受保护的资源）时，如果系统检测到用户仍然活跃，可以主动续签 Token，避免用户在未操作期间 Token 过期。
            // Token 刷新策略：应用可能设计了刷新 Token 的机制，例如使用短期有效的 Access Token 和长期有效的 Refresh Token。Access Token 过期时，通过 Refresh Token 获取新的 Access T
            // 安全策略变化：如果系统的安全策略发生了变化（如用户权限更新），可以选择重新签发 Token，以反映最新的权限状态。

            jwtTokenRedisCacheProvider.checkRenewal(_jwtToken);
        }
        //放行
        filterChain.doFilter(request, response);
    }
}
